// This file is part of Keepass2Android, Copyright 2025 Philipp Crocoll.
//
//   Keepass2Android is free software: you can redistribute it and/or modify
//   it under the terms of the GNU General Public License as published by
//   the Free Software Foundation, either version 3 of the License, or
//   (at your option) any later version.
//
//   Keepass2Android is distributed in the hope that it will be useful,
//   but WITHOUT ANY WARRANTY; without even the implied warranty of
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   GNU General Public License for more details.
//
//   You should have received a copy of the GNU General Public License
//   along with Keepass2Android.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using Android.Content;
using Android.OS;
using Java.IO;
using KeePassLib.Serialization;
using KeePassLib.Utility;

namespace keepass2android.Io
{
  public static class IoUtil
  {

    public static bool TryTakePersistablePermissions(ContentResolver contentResolver, Android.Net.Uri uri)
    {
      if ((int)Build.VERSION.SdkInt >= 19)
      {
        //try to take persistable permissions
        try
        {
          Kp2aLog.Log("TakePersistableUriPermission");
          var takeFlags = (ActivityFlags.GrantReadUriPermission
                  | ActivityFlags.GrantWriteUriPermission);
          contentResolver.TakePersistableUriPermission(uri, takeFlags);
          return true;
        }
        catch (Exception e)
        {
          Kp2aLog.Log(e.ToString());
        }

      }
      return false;
    }
    public static bool DeleteDir(Java.IO.File dir, bool contentsOnly = false)
    {
      if (dir != null && dir.IsDirectory)
      {
        String[] children = dir.List();
        for (int i = 0; i < children.Length; i++)
        {
          bool success = DeleteDir(new Java.IO.File(dir, children[i]));
          if (!success)
          {
            return false;
          }
        }
      }

      if (contentsOnly)
        return true;

      // The directory is now empty so delete it
      return dir.Delete();
    }


    public static IOConnectionInfo GetParentPath(IOConnectionInfo ioc)
    {
      var iocParent = ioc.CloneDeep();
      if (iocParent.Path.EndsWith("/"))
        iocParent.Path = iocParent.Path.Substring(0, iocParent.Path.Length - 1);

      int slashPos = iocParent.Path.LastIndexOf("/", StringComparison.Ordinal);
      if (slashPos == -1)
        iocParent.Path = "";
      else
      {
        iocParent.Path = iocParent.Path.Substring(0, slashPos);
      }
      return iocParent;
    }

    public static bool IsInInternalDirectory(string path, Context context)
    {
      try
      {
        Java.IO.File filesDir = context.FilesDir.CanonicalFile;
        Java.IO.File noBackupDir = GetInternalDirectory(context).CanonicalFile;
        Java.IO.File ourFile = new Java.IO.File(path).CanonicalFile;
        //http://www.java2s.com/Tutorial/Java/0180__File/Checkswhetherthechilddirectoryisasubdirectoryofthebasedirectory.htm

        Java.IO.File parentFile = ourFile;
        while (parentFile != null)
        {
          if ((filesDir.Equals(parentFile) || noBackupDir.Equals(parentFile)))
          {
            return true;
          }
          parentFile = parentFile.ParentFile;
        }
        return false;
      }
      catch (Exception e)
      {
        Kp2aLog.LogUnexpectedError(e);
        return false;
      }

    }

    public static void Copy(IOConnectionInfo targetIoc, IOConnectionInfo sourceIoc, IKp2aApp app)
    {
      IFileStorage sourceStorage = app.GetFileStorage(sourceIoc, false); //don't cache source. file won't be used ever again
      IFileStorage targetStorage = app.GetFileStorage(targetIoc);

      using (
          var writeTransaction = targetStorage.OpenWriteTransaction(targetIoc,
                                                                    app.GetBooleanPreference(
                                                                        PreferenceKey.UseFileTransactions)))
      {
        using (var writeStream = writeTransaction.OpenFile())
        {
          MemUtil.CopyStream(sourceStorage.OpenFileForRead(sourceIoc), writeStream);
        }
        writeTransaction.CommitWrite();
      }
    }

    public static Java.IO.File GetInternalDirectory(Context ctx)
    {
      if ((int)Android.OS.Build.VERSION.SdkInt >= 21)
        return ctx.NoBackupFilesDir;
      else
        return ctx.FilesDir;
    }

    //creates a local ioc where the sourceIoc can be stored to
    public static IOConnectionInfo GetInternalIoc(IOConnectionInfo sourceIoc, Context ctx, IKp2aApp app)
    {
      Java.IO.File internalDirectory = IoUtil.GetInternalDirectory(ctx);
      var filestorage = app.GetFileStorage(sourceIoc);

      string targetPath = filestorage.GetFilenameWithoutPathAndExt(sourceIoc);
      targetPath = targetPath.Trim("|\\?*<\":>+[]/'".ToCharArray());
      if (targetPath == "")
        targetPath = "internal";
      if (new Java.IO.File(internalDirectory, targetPath).Exists())
      {
        int c = 1;
        var ext = UrlUtil.GetExtension(targetPath);
        var filenameWithoutExt = UrlUtil.StripExtension(targetPath);
        do
        {
          c++;
          targetPath = filenameWithoutExt + c;
          if (!String.IsNullOrEmpty(ext))
            targetPath += "." + ext;
        } while (new Java.IO.File(internalDirectory, targetPath).Exists());
      }
      return IOConnectionInfo.FromPath(new Java.IO.File(internalDirectory, targetPath).CanonicalPath);
    }

    public static IOConnectionInfo ImportFileToInternalDirectory(IOConnectionInfo sourceIoc, Context ctx, IKp2aApp app)
    {
      var targetIoc = GetInternalIoc(sourceIoc, ctx, app);


      IoUtil.Copy(targetIoc, sourceIoc, app);
      return targetIoc;
    }

    public static string GetIocPrefKey(IOConnectionInfo ioc, string suffix)
    {
      var iocAsHexString = IocAsHexString(ioc);

      return "kp2a_ioc_key_" + iocAsHexString + suffix;
    }


    public static string IocAsHexString(IOConnectionInfo ioc)
    {
      SHA256Managed sha256 = new SHA256Managed();
      string iocAsHexString =
          MemUtil.ByteArrayToHexString(sha256.ComputeHash(Encoding.Unicode.GetBytes(ioc.Path.ToCharArray())));
      return iocAsHexString;
    }
  }
}
